/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.activemq.console.command; import java.io.File; import java.io.IOException; import java.lang.management.ManagementFactory; import java.lang.reflect.Method; import java.net.ConnectException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import javax.management.MBeanServerConnection; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; public abstract class AbstractJmxCommand extends AbstractCommand { public static String DEFAULT_JMX_URL; private static String jmxUser; private static String jmxPassword; private static boolean jmxUseLocal; private static final String CONNECTOR_ADDRESS = "com.sun.management.jmxremote.localConnectorAddress"; private JMXServiceURL jmxServiceUrl; private JMXConnector jmxConnector; private MBeanServerConnection jmxConnection; static { DEFAULT_JMX_URL = System.getProperty("activemq.jmx.url", "service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi"); jmxUser = System.getProperty("activemq.jmx.user"); jmxPassword = System.getProperty("activemq.jmx.password"); jmxUseLocal = Boolean.parseBoolean(System.getProperty("activemq.jmx.useLocal", "false")); } /** * Get the current specified JMX service url. * @return JMX service url */ protected JMXServiceURL getJmxServiceUrl() { return jmxServiceUrl; } public static String getJVM() { return System.getProperty("java.vm.specification.vendor"); } public static boolean isSunJVM() { // need to check for Oracle as that is the name for Java7 onwards. return getJVM().equals("Sun Microsystems Inc.") || getJVM().startsWith("Oracle"); } /** * Finds the JMX Url for a VM by its process id * * @param pid * The process id value of the VM to search for. * * @return the JMX Url of the VM with the given pid or null if not found. */ @SuppressWarnings({ "rawtypes", "unchecked" }) protected String findJMXUrlByProcessId(int pid) { if (isSunJVM()) { try { // Classes are all dynamically loaded, since they are specific to Sun VM // if it fails for any reason default jmx url will be used // tools.jar are not always included used by default class loader, so we // will try to use custom loader that will try to load tools.jar String javaHome = System.getProperty("java.home"); String tools = javaHome + File.separator + ".." + File.separator + "lib" + File.separator + "tools.jar"; URLClassLoader loader = new URLClassLoader(new URL[]{new File(tools).toURI().toURL()}); Class virtualMachine = Class.forName("com.sun.tools.attach.VirtualMachine", true, loader); Class virtualMachineDescriptor = Class.forName("com.sun.tools.attach.VirtualMachineDescriptor", true, loader); Method getVMList = virtualMachine.getMethod("list", (Class[])null); Method attachToVM = virtualMachine.getMethod("attach", String.class); Method getAgentProperties = virtualMachine.getMethod("getAgentProperties", (Class[])null); Method getVMId = virtualMachineDescriptor.getMethod("id", (Class[])null); List allVMs = (List)getVMList.invoke(null, (Object[])null); for(Object vmInstance : allVMs) { String id = (String)getVMId.invoke(vmInstance, (Object[])null); if (id.equals(Integer.toString(pid))) { Object vm = attachToVM.invoke(null, id); Properties agentProperties = (Properties)getAgentProperties.invoke(vm, (Object[])null); String connectorAddress = agentProperties.getProperty(CONNECTOR_ADDRESS); if (connectorAddress != null) { return connectorAddress; } else { break; } } } } catch (Exception ignore) { } } return null; } /** * Get the current JMX service url being used, or create a default one if no JMX service url has been specified. * @return JMX service url * @throws MalformedURLException */ @SuppressWarnings({ "rawtypes", "unchecked" }) protected JMXServiceURL useJmxServiceUrl() throws MalformedURLException { if (getJmxServiceUrl() == null) { String jmxUrl = DEFAULT_JMX_URL; int connectingPid = -1; if (isSunJVM()) { try { // Classes are all dynamically loaded, since they are specific to Sun VM // if it fails for any reason default jmx url will be used // tools.jar are not always included used by default class loader, so we // will try to use custom loader that will try to load tools.jar String javaHome = System.getProperty("java.home"); String tools = javaHome + File.separator + ".." + File.separator + "lib" + File.separator + "tools.jar"; URLClassLoader loader = new URLClassLoader(new URL[]{new File(tools).toURI().toURL()}); Class virtualMachine = Class.forName("com.sun.tools.attach.VirtualMachine", true, loader); Class virtualMachineDescriptor = Class.forName("com.sun.tools.attach.VirtualMachineDescriptor", true, loader); Method getVMList = virtualMachine.getMethod("list", (Class[])null); Method attachToVM = virtualMachine.getMethod("attach", String.class); Method getAgentProperties = virtualMachine.getMethod("getAgentProperties", (Class[])null); Method getVMDescriptor = virtualMachineDescriptor.getMethod("displayName", (Class[])null); Method getVMId = virtualMachineDescriptor.getMethod("id", (Class[])null); List allVMs = (List)getVMList.invoke(null, (Object[])null); for(Object vmInstance : allVMs) { String displayName = (String)getVMDescriptor.invoke(vmInstance, (Object[])null); if (displayName.contains("activemq.jar start")) { String id = (String)getVMId.invoke(vmInstance, (Object[])null); Object vm = attachToVM.invoke(null, id); Properties agentProperties = (Properties)getAgentProperties.invoke(vm, (Object[])null); String connectorAddress = agentProperties.getProperty(CONNECTOR_ADDRESS); if (connectorAddress != null) { jmxUrl = connectorAddress; connectingPid = Integer.parseInt(id); context.print("useJmxServiceUrl Found JMS Url: " + jmxUrl); break; } } } } catch (Exception ignore) { } } if (connectingPid != -1) { context.print("Connecting to pid: " + connectingPid); } else { context.print("Connecting to JMX URL: " + jmxUrl); } setJmxServiceUrl(jmxUrl); } return getJmxServiceUrl(); } /** * Sets the JMX service url to use. * @param jmxServiceUrl - new JMX service url to use */ protected void setJmxServiceUrl(JMXServiceURL jmxServiceUrl) { this.jmxServiceUrl = jmxServiceUrl; } /** * Sets the JMX service url to use. * @param jmxServiceUrl - new JMX service url to use * @throws MalformedURLException */ protected void setJmxServiceUrl(String jmxServiceUrl) throws MalformedURLException { setJmxServiceUrl(new JMXServiceURL(jmxServiceUrl)); } /** * Get the JMX user name to be used when authenticating. * @return the JMX user name */ public String getJmxUser() { return jmxUser; } /** * Sets the JMS user name to use * @param jmxUser - the jmx */ public void setJmxUser(String jmxUser) { AbstractJmxCommand.jmxUser = jmxUser; } /** * Get the password used when authenticating * @return the password used for JMX authentication */ public String getJmxPassword() { return jmxPassword; } /** * Sets the password to use when authenticating * @param jmxPassword - the password used for JMX authentication */ public void setJmxPassword(String jmxPassword) { AbstractJmxCommand.jmxPassword = jmxPassword; } /** * Get whether the default mbean server for this JVM should be used instead of the jmx url * @return <code>true</code> if the mbean server from this JVM should be used, <code>false<code> if the jmx url should be used */ public boolean isJmxUseLocal() { return jmxUseLocal; } /** * Sets whether the the default mbean server for this JVM should be used instead of the jmx url * @param jmxUseLocal - <code>true</code> if the mbean server from this JVM should be used, <code>false<code> if the jmx url should be used */ public void setJmxUseLocal(boolean jmxUseLocal) { AbstractJmxCommand.jmxUseLocal = jmxUseLocal; } /** * Create a JMX connector using the current specified JMX service url. If there is an existing connection, * it tries to reuse this connection. * @return created JMX connector * @throws IOException */ private JMXConnector createJmxConnector() throws IOException { // Reuse the previous connection if (jmxConnector != null) { jmxConnector.connect(); return jmxConnector; } // Create a new JMX connector if (jmxUser != null && jmxPassword != null) { Map<String,Object> props = new HashMap<String,Object>(); props.put(JMXConnector.CREDENTIALS, new String[] { jmxUser, jmxPassword }); jmxConnector = JMXConnectorFactory.connect(useJmxServiceUrl(), props); } else { jmxConnector = JMXConnectorFactory.connect(useJmxServiceUrl()); } return jmxConnector; } /** * Close the current JMX connector */ protected void closeJmxConnection() { try { if (jmxConnector != null) { jmxConnector.close(); jmxConnector = null; } } catch (IOException e) { } } protected MBeanServerConnection createJmxConnection() throws IOException { if (jmxConnection == null) { if (isJmxUseLocal()) { jmxConnection = ManagementFactory.getPlatformMBeanServer(); } else { jmxConnection = createJmxConnector().getMBeanServerConnection(); } } return jmxConnection; } /** * Handle the --jmxurl option. * @param token - option token to handle * @param tokens - succeeding command arguments * @throws Exception */ @Override protected void handleOption(String token, List<String> tokens) throws Exception { // Try to handle the options first if (token.equals("--jmxurl")) { // If no jmx url specified, or next token is a new option if (tokens.isEmpty() || tokens.get(0).startsWith("-")) { context.printException(new IllegalArgumentException("JMX URL not specified.")); } // If jmx url already specified if (getJmxServiceUrl() != null) { context.printException(new IllegalArgumentException("Multiple JMX URL cannot be specified.")); tokens.clear(); } String strJmxUrl = tokens.remove(0); try { this.setJmxServiceUrl(new JMXServiceURL(strJmxUrl)); } catch (MalformedURLException e) { context.printException(e); tokens.clear(); } } else if(token.equals("--pid")) { if (isSunJVM()) { if (tokens.isEmpty() || tokens.get(0).startsWith("-")) { context.printException(new IllegalArgumentException("pid not specified")); return; } int pid = Integer.parseInt(tokens.remove(0)); context.print("Connecting to pid: " + pid); String jmxUrl = findJMXUrlByProcessId(pid); if (jmxUrl != null) { // If jmx url already specified if (getJmxServiceUrl() != null) { context.printException(new IllegalArgumentException("JMX URL already specified.")); tokens.clear(); } try { this.setJmxServiceUrl(new JMXServiceURL(jmxUrl)); } catch (MalformedURLException e) { context.printException(e); tokens.clear(); } } else { context.printInfo("failed to resolve jmxUrl for pid:" + pid + ", using default JMX url"); } } else { context.printInfo("--pid option is not available for this VM, using default JMX url"); } } else if (token.equals("--jmxuser")) { // If no jmx user specified, or next token is a new option if (tokens.isEmpty() || tokens.get(0).startsWith("-")) { context.printException(new IllegalArgumentException("JMX user not specified.")); } this.setJmxUser(tokens.remove(0)); } else if (token.equals("--jmxpassword")) { // If no jmx password specified, or next token is a new option if (tokens.isEmpty() || tokens.get(0).startsWith("-")) { context.printException(new IllegalArgumentException("JMX password not specified.")); } this.setJmxPassword(tokens.remove(0)); } else if (token.equals("--jmxlocal")) { this.setJmxUseLocal(true); } else { // Let the super class handle the option super.handleOption(token, tokens); } } @Override public void execute(List<String> tokens) throws Exception { try { super.execute(tokens); } catch (Exception exception) { handleException(exception, jmxServiceUrl.toString()); return; }finally { closeJmxConnection(); } } }